﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Kurogane.Compiler {
	public class Parser {

		private readonly Lexer _lexer;

		// 解析に一時的に使われる
		private Stack<Action<Block>> _blockStack = new Stack<Action<Block>>();
		private List<CaseSentence> _caseList = new List<CaseSentence>();
		private int _level = 0;

		public Parser(Lexer lexer) {
			_lexer = lexer;
		}

		public Block Parse() {
			_lexer.MoveNext(); // 先読み
			return ParseBlock();
		}

		private Block ParseBlock() {
			var result = new Block();
			while (_caseList.Count <= _level) _caseList.Add(null);
			while (true) {
				if (_lexer.Current == null) break;
				if (_lexer.Current is ReservedToken && ((ReservedToken)_lexer.Current).Value == "以上") {
					_lexer.MoveNext();
					if (_lexer.Current is PunctuationToken) {
						_lexer.MoveNext();
						break;
					}
					else {
						throw new ParseException("「以上」の後ろには句点を入れてください");
					}
				}
				result.Contents.Add(ParseSentence());
			}
			return result;
		}



		private Sentence ParseSentence() {
			CaseSentence caseAtom = null;
			var result = new NormalSentence();
			if (_lexer.Current is ReservedToken && ((ReservedToken)_lexer.Current).Value == "もし") {
				caseAtom = new CaseSentence();
				var cond = ReadConditionAtom();
				var when = new WhenAtom(cond, result);
				caseAtom.Whens.Add(when);
			}
			while (true) {
				SentenceAtom atom = ReadSentenceAtom();
				result.Contents.Add(atom);
				PunctuationToken punc = (PunctuationToken)_lexer.Current;
				_lexer.MoveNext();
				if (punc.Value == "。" || punc.Value == ".") break;
			}
			if (_blockStack.Count > _level) {
				_level++;
				var block = ParseBlock();
				_level--;
				var action = _blockStack.Pop();
				action(block);
			}
			if (caseAtom != null) {
				result = new NormalSentence();
				return result;
			}
			else {
				return result;
			}
		}

		private ActionAtom ReadConditionAtom() {
			throw new NotImplementedException();
		}

		private SentenceAtom ReadSentenceAtom() {
			ActionAtom atom = new ActionAtom();
			while (true) {
				Token token = _lexer.Current;
				if (token is TargetToken || token is OpenParenthesisToken) {
					Target t = ReadTarget();
					Token post = _lexer.Current;
					if (post is PostPositionToken) {
						var pp = (PostPositionToken)post;
						_lexer.MoveNext();
						atom.Arguments[pp.Value] = t;
						continue;
					}
					if (post is ReservedToken) {
						var res = (ReservedToken)post;
						if (res.Value == "する" || res.Value == "し") {
							atom.Action = t;
							_lexer.MoveNext();
							return atom;
						}
					}
				}
				if (token is ReservedToken) {
					var res = (ReservedToken)token;
					if (res.Value == "する" || res.Value == "し") {
						_lexer.MoveNext();
						// 代入文だとみなす
						return new AssignmentAtom { Arguments = atom.Arguments };
					}
					if (res.Value == "以下") {
						_lexer.MoveNext();
						var _postPos = _lexer.Current;
						if (_postPos.Value == "の") {
							_lexer.MoveNext();
							if (_lexer.Current is ReservedToken) {
								var blockAction = (ReservedToken)_lexer.Current;
								if (blockAction.Value == "手順") { // 関数宣言
									_lexer.MoveNext();
									_postPos = (PostPositionToken)_lexer.Current;
									if (_postPos.Value != "で") {
										throw new ParseException("「で」じゃないです。");
									}
									_lexer.MoveNext();
									var proc = ReadDefProc();
									var arg = new Dictionary<string, Target>();
									arg["を"] = proc;
									arg["と"] = new ReferenceTarget(proc.Name);
									_blockStack.Push(t => proc.Block = t);
									return new AssignmentAtom { Arguments = arg };
								}
							}
						}
					}
				}
				throw new ParseException("不明なトークンが来ています。" + Environment.NewLine + token);
			}
		}

		/// <summary>
		/// 関数宣言を解析する。
		/// lexerの先頭は「以下」を含む文の直後。
		/// </summary>
		/// <returns></returns>
		private Proc ReadDefProc() {
			if (_lexer.Current is PunctuationToken) _lexer.MoveNext();

			var param = new Dictionary<string, string>();
			while (true) {
				var argTarget = new ReferenceTarget(((SymbolToken)_lexer.Current).Value);
				_lexer.MoveNext();
				var post = _lexer.Current;
				if (post is PostPositionToken) {
					param[((PostPositionToken)post).Value] = argTarget.Name;
					_lexer.MoveNext();
					continue;
				}
				if (post is ReservedToken) {
					var res = (ReservedToken)post;
					if (res.Value == "する" || res.Value == "し") {
						_lexer.MoveNext();
						return new Proc {
							Parameter = param,
							Name = argTarget.Name,
						};
					}
				}
				throw new ParseException();
			}
		}

		private Target ReadTarget() {
			var current = _lexer.Current;
			if (current is SymbolToken) {
				_lexer.MoveNext();
				return new ReferenceTarget(((SymbolToken)current).Value);
			}
			if (current is LiteralToken) {
				_lexer.MoveNext();
				return new LiteralTarget(((LiteralToken)current).Value);
			}
			if (current is OpenParenthesisToken) {
				_lexer.MoveNext();
				var exp = ReadExpression();
				if (!(_lexer.Current is CloseParenthesisToken)) {
					throw new ParseException("閉じ括弧が正しく閉じられませんでした。");
				}
				_lexer.MoveNext();
				return exp;
			}
			if (current is NumberToken) {
				_lexer.MoveNext();
				if (current is IntegerToken)
					return new LiteralTarget(((IntegerToken)current).IntValue);
				if (current is DecimalToken)
					return new LiteralTarget(((DecimalToken)current).DecimalValue);
			}
			throw new ParseException("想定外のトークンが現れました");
		}

		private Target ReadExpression() {
			string op = null;
			if (_lexer.Current is OperatorToken) {
				op = ((OperatorToken)_lexer.Current).Value;
				_lexer.MoveNext();
			}
			var left = ReadTarget();
			if (op != null) {
				left = new UnaryOperation(op, left);
			}
			if (_lexer.Current is OperatorToken) {
				op = ((OperatorToken)_lexer.Current).Value;
				_lexer.MoveNext();
				var right = ReadTarget();
				return new BinaryOperation(left, op, right);
			}
			else {
				return left;
			}
		}
	}

	public class ParseException : Exception {
		public ParseException() : base() { }
		public ParseException(string message) : base(message) { }
	}

}
